//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
#include "ddk.h"

#include <stdio.h>
#include <time.h>
#include <ctype.h>
#include <stdlib.h>
#include <tfssys.h>
#include <misc.h>
#include <fat.h>

/* Mark dirtymap at given position */
#define DIRTY(idx)         (dirtymap[(idx * sizeof(fat32_t)) / p_Sharedata->secsz] = 1)

void fat32::
fat32_clearfatseg(void)
{
    for (UInt32 i = 0; i < nfatv; i++) {
        if (fatv[i] != NULL) {
            free(fatv[i]);
            fatv[i] = NULL;
        }
    }
}

/*
 * fat32_loadfat()
 *    Load FAT handling
 *
 * Read in the FAT, convert if needed.
 */
int fat32::
fat32_loadfat(class fatfs_sharedata *pSharedata)
{
    if (fatv) {
        fat32_clearfatseg();
        fatseg = 0;
    }
    return 0;
}

/*
 * fat32_init()
 *    Initialize FAT handling
 *
 * Read in the FAT, convert if needed.
 */
int fat32::
fat32_init(class fatfs_sharedata *pSharedata)
{
    UInt32 x;

    ASSERT_DEBUG(pSharedata!=NULL, "fat_setlen: bad clust");
    p_Sharedata = pSharedata;
    p_Sharedata->AddRef();

    /*
     * Calculate some global parameters
     */
    x = p_Sharedata->bootb.BPB_TotSec16_0 +
        (p_Sharedata->bootb.BPB_TotSec16_1 << 8);
    p_Sharedata->data0 = p_Sharedata->bootb.BPB_ResvdSecCnt +
            p_Sharedata->bootb.boottype.boot32.BPB_FATSz32*
            p_Sharedata->bootb.BPB_NumFATs;
    p_Sharedata->root_cluster =
        p_Sharedata->bootb.boottype.boot32.BPB_RootClus;

    //ASSERT_DEBUG(p_Sharedata->root_cluster != 0,
    //"fat32_init: zero root cluster");
    if (p_Sharedata->root_cluster == 0) {
        return 1;
    }

    if (x > 0) {
        p_Sharedata->nclust = (x * p_Sharedata->secsz) / p_Sharedata->clsize;
    }
    else {
        p_Sharedata->nclust = (p_Sharedata->bootb.BPB_TotSec32 -
                p_Sharedata->data0) / p_Sharedata->bootb.BPB_SecPerClus;
    }

    /*
     * Get map of dirty sectors in FAT
     */
    dirtymapsize = roundup(p_Sharedata->nclust*sizeof(fat32_t), p_Sharedata->secsz) / p_Sharedata->secsz;
    dirtymap = (UInt8 *)malloc(dirtymapsize);
    ASSERT_DEBUG(dirtymap, "fat32_init: dirtymap");
    bzero(dirtymap, dirtymapsize);

    /*
     * Get memory for FAT table
     */
    fatbase = p_Sharedata->bootb.BPB_ResvdSecCnt;
    fatlen = p_Sharedata->bootb.boottype.boot32.BPB_FATSz32 * p_Sharedata->secsz;
    nfatv = roundup(fatlen, FATSEGSIZE) / FATSEGSIZE;
    fatvlen = nfatv * sizeof(fat32_t *);
    fatv = (fat32_t **)malloc(fatvlen);
    if (fatv == 0) {
        return -1;
    }
    bzero(fatv, fatvlen);

    /*
     * If there's an info sector, get it, too
     */
    infobase = p_Sharedata->bootb.boottype.boot32.BPB_FSInfo;
    if (infobase != 0xFFFF) {
        /*
         * Read in the sector
         */
        infosize = roundup(sizeof(fat32_info), p_Sharedata->secsz);
        info = (fat32_info *)malloc(infosize);

        ECode ec = ReadDevice(
                    p_Sharedata->pBlkDev,
                    (infobase+p_Sharedata->bpb_base) * p_Sharedata->secsz, infosize,
                    (PByte)info, NULL, NULL);
        if (FAILED(ec)) {
            DPRINTF(("ERROR: Failed to Read from the offset:%d", p_Sharedata->secsz));
            return -1;
        }

        /*
         * Sanity check
         */
        if ((info->signature0 != INFOSECT_SIGNATURE0) ||
            (info->signature1 != INFOSECT_SIGNATURE1)) {
            DPRINTF(("ERROR:corrupt info sector\n"));
            infobase = 0;
        }
        nxt_clust = info->last + 1;
        if (nxt_clust > (p_Sharedata->nclust + 1)) {
            nxt_clust = 2;
        }
    }
    else {
        /*
         * Flag no info sector
         */
        info = 0;
    }

    return 0;
}

/*
 * lookup()
 *    Return pointer to fat32_t slot for this index value
 *
 * Uses fatv[] to permit only active parts of the FAT table
 * to reside in memory.  TBD: remove parts not recently used?
 * Creates any needed chunk of fat32_t entries on demand.
 */
fat32_t * fat32::
lookup(UInt32 idx)
{
    UInt32 seg = FATSEG(idx);
    fat32_t *fatp, *ptr;
    off_t off;

    /*
     * If it isn't there yet, need to go fetch it now
     */
    if (seg >= nfatv) {
        DPRINTF(("bad seg %d limit %d idx %ld\n", seg, nfatv, idx));
        return NULL;
    }
    ASSERT_DEBUG(seg < nfatv, "fat32 lookup: bad index");
    fatp = fatv[seg];
    if (fatp == 0) {
        if (fatseg == FATSEGMAX) {
            Int32 i = seg;
            do {
                --i;
                if (i < 0) i = nfatv - 1;
                if (fatv[i] != NULL) break;
            } while (i != (Int32)seg);

            Int64 offset = (fatbase * p_Sharedata->secsz) + i * FATSEGSIZE +
                            p_Sharedata->bpb_base * p_Sharedata->secsz;
    
            ECode ec = WriteDevice(
                p_Sharedata->pBlkDev, offset, FATSEGSIZE, (PByte)fatv[i],
                NULL, NULL);
            if (FAILED(ec)) {
                DPRINTF(("ERROR: Failed to Write the offset:%d\n", offset));
            }

            free(fatv[i]);
            fatv[i] = NULL;
            fatseg -= 1;
        }

        /*
         * Allocate this chunk
         */
        fatp = fatv[seg] = (fat32_t *)malloc(FATSEGSIZE);
        if (NULL == fatp) {
            return NULL;
        }
        fatseg += 1;

        fatv[seg] = fatp;

        /*
         * Seek over in the FAT and read it
         */
        off = (fatbase * p_Sharedata->secsz) + (seg * FATSEGSIZE) +
            p_Sharedata->bpb_base*p_Sharedata->secsz;

        ECode ec = ReadDevice(
                    p_Sharedata->pBlkDev, off, FATSEGSIZE, (PByte)fatp,
                    NULL, NULL);
        if (FAILED(ec)) {
            DPRINTF(("ERROR: Failed to Read from the offset:%d\n", off));
            return NULL;
        }
    }

    /*
     * Now return the particular entry within
     */
    ptr = &fatp[FATIDX(idx)];
    ASSERT_DEBUG((ptr >= fatp) &&
        (ptr < (fat32_t *)((char *)fatp + FATSEGSIZE)),
        "fat32 lookup(): bad index");
    return(ptr);
}

/*
 * get()
 *    Access a FAT-32 entry
 */
fat32_t fat32::
get(UInt32 idx)
{
    //BUGBUG:Returning 0x0ffffffe is just a temporary choice.
    fat32_t *p = lookup(idx);
    if (NULL == p) {
        return 0x0ffffffe;
    }
    else {
        return((*p) & 0x0fffffff);
    }
}

/*
 * set()
 *    Set a FAT-32 entry, Note a FAT32 FAT entry is actually only a 28-bit
 *    entry
 *    .The high 4-bit of a FAT32 FAT entry are reserved.The only time that the
 *    high 4-bits of FAT32 FAT entrys should be changed is when the volume is
 *    formatted.
 */
void fat32::
set(UInt32 idx, fat32_t val)
{
    //BUGBUG:Do not handle the case that lookup failed.
    fat32_t* target = lookup(idx);
    assert(target != NULL);
    (*target) &= 0xf0000000;
    val &= 0x0fffffff;
    (*target) |= val;
    DIRTY(idx);
}

/*
 * fat32_setlen()
 *    Twiddle FAT allocation to match the indicated length
 *
 * Returns 0 if it could be done; 1 if it failed.
 */
int fat32::
fat32_setlen(struct clust *c, UInt32 newclust)
{
    UInt32 clust_cnt, x;
    UInt32 cl, *ctmp;

    /*
     * Getting smaller--free stuff at the end
     */
    if (c->c_nclust > newclust) {
        /*
         * it is very surprise to find this expression, nxt_clust may be
         * invalid
         */
        //nxt_clust = newclust;  ????
        nxt_clust = c->c_clust[newclust];

        for (x = newclust; x < c->c_nclust;++x) {
            cl = c->c_clust[x];
#ifdef _DEBUG
            if ((cl > p_Sharedata->nclust + 1) || (cl < 2)) {
                UInt32 z;

                DPRINTF(("ERROR:bad cluster 0x%x\n", cl));
                DPRINTF(("ERROR:clusters in file:"));
                for (z = 0; z < c->c_nclust;++z) {
                    DPRINTF((" %x", c->c_clust[z]));
                }
                break;//acceptance some error
                //eg:if delete a file but the file is not end appropriately
                //with EOF. Its cluster maybe pointes to 0 cluster
                ASSERT_DEBUG(0, "fat_setlen: bad clust");
            }
#endif
            set(cl, 0);
            c->c_clust[x] = 0;
            DeleteBlock( p_Sharedata->pBlkDev, BOFF(cl),
                            p_Sharedata->bootb.BPB_SecPerClus);
        }
        if (info) {
            info->free += (c->c_nclust - newclust);
        }
        if (newclust > 0) {
            set(c->c_clust[newclust-1], FAT32_EOF);
        }
        if (newclust < c->n_newclust)
            c->n_newclust = 0x7fffffff;    // new added
        c->c_nclust = newclust;
        p_Sharedata->fat_dirty = 1;
        return(0);
    }

    /*
     * Trying to grow.  See if we can get the blocks.  If we can't,
     * we bail out.  The allocation is done in two passes, so that
     * when we bail after running out of space there's nothing which
     * needs to be undone.  The realloc()'ed c_clust is harmless.
     */
    //ctmp = (UInt32 *)realloc(c->c_clust, newclust * sizeof(UInt32));
    ctmp = (UInt32 *)malloc(newclust * sizeof(UInt32));
    if (ctmp == 0) {
        return(1);
    }
    memcpy(ctmp, c->c_clust, c->c_nclust * sizeof(UInt32));
    if (c->c_clust) {
        free(c->c_clust);
    }
    c->c_clust = ctmp;
    cl = nxt_clust;
    clust_cnt = 0;
    for (x = c->c_nclust; x < newclust;++x) {
        /*
         * Scan for next free cluster
         */
        while ((clust_cnt++< p_Sharedata->nclust) && get(cl)) {
            if (++cl > p_Sharedata->nclust + 1) {
                cl = 2;
            }
        }

        /*
         * If we didn't find one, fail
         */
        if (clust_cnt > p_Sharedata->nclust) {
            return(1);
        }

        /*
         * Tag where last search finished to save time
         */
        nxt_clust = cl + 1;
        if (nxt_clust > p_Sharedata->nclust + 1) {
            nxt_clust = 2;
        }

        /*
         * Sanity
         */
        if ((cl > p_Sharedata->nclust + 1) || (cl < 2)) {
            break;//acceptance some error
            //eg:if growing a file occur sudden death and it get into
            //debug, the file will end without EOF.SO break will allow
            //the pogram continue the file end with EOF(reduce bad chain)
        }
        //ASSERT_DEBUG((cl >= 2) && (cl < p_Sharedata->nclust),
        //    "clust_setlen: bad FAT");

        /*
         * Otherwise add it to our array.  We will flag it
         * consumed soon.
         */
        ctmp[x] = cl++;//cl++ for searching next cluster : used in get(cl)

        /*
         * clust 0, 1 are both reserved, so jump to clust 2 will be more safe??
         */
        if (cl > p_Sharedata->nclust + 1) {
            cl = 2;
        }
    }

    /*
     * When we get here, the new clusters for the file extension
     * have been found and filled into the c_clust[] array.
     * We now go back and (1) flag the FAT entries consumed,
     * which also builds the cluster chain, and (2) update the
     * count of clusters for this file.
     */

    /*
     * Chain last block which already existed onto this new
     * space.
     */
    if (c->c_nclust > 0) {
        set(c->c_clust[c->c_nclust-1], c->c_clust[c->c_nclust]);
    }

    /*
     * Chain all the new clusters together
     */
    for (x = c->c_nclust; x < newclust-1;++x) {
        set(c->c_clust[x], c->c_clust[x+1]);
    }

    /*
     * Update free list count
     */
    if (info) {
        info->free -= (newclust - c->c_nclust);
    }

    /*
     * Mark the EOF cluster for the last one
     */
    set(c->c_clust[newclust-1], FAT32_EOF);
    if (c->n_newclust > newclust)
        c->n_newclust = c->c_nclust-1;    // new added
    c->c_nclust = newclust;
    p_Sharedata->fat_dirty = 1;
    return(0);
}

/*
 * fat32_alloc()
 *    Allocate a description of the given cluster chain
 */
struct clust * fat32::
fat32_alloc(struct clust *c, struct directory *d)
{
    UInt32 nclust = 0, start, x;
#ifdef _DEBUG
    UInt32 prev = 0;
#endif
    /*
     * Get starting cluster from directory entry
     */
    start = START(d);

    /*
     * Scan the chain to get its length
     */

    for (x = start; ((x < FAT32_RESERVED) && (x >= 2)); x = get(x)) {
        if (x < 2) {
#ifdef _DEBUG
            DPRINTF(("Lose Chain AT Cluster#0x%08x\n", prev));
#endif
            //acceptance some error.eg:if the file end without EOF,lose chain
            break;
        }
        //ASSERT_DEBUG(x >= 2, "alloc_clust: free cluster in file");
        nclust++;
#ifdef _DEBUG
        prev = x;
#endif
    }

    /*
     * Allocate the description array
     */

    c->c_clust = (UInt32 *)malloc(nclust * sizeof(UInt32));
    if (c->c_clust == 0) {
        return(0);
    }
    c->c_nclust = nclust;

    /*
     * Scan again, recording each cluster number
     */
    x = start;
    nclust = 0;
    do {
        c->c_clust[nclust++] = x;
        x = get(x);
    } while ( (x >= 2) && (x < FAT32_RESERVED) );

    return(c);
}

/*
 * fat32_sync()
 *    Write a FAT32 using the dirtymap to minimize I/O
 */
void fat32::
fat32_sync(void)
{
    UInt32 x, cnt, pass, baseSeg, fatsize;
    off_t off;
    fat32_t *fatp;

    fatsize = (fatlen+p_Sharedata->secsz-1) >> p_Sharedata->secshift;
    /*
     * There are two copies of the FAT, so do them iteratively
     */
    for (pass = 0; pass <= 1;++pass) {
        /*
         * Calculate the offset once per pass
         */
        off = pass*(UInt32)fatlen;

        /*
         * Walk across the dirty map, find the next dirty sector
         * of FAT information to write out.
         */
        for (x = 0; x < dirtymapsize;) {
            /*
             * Not dirty.  Advance to next sector's worth.
             */
            if (!dirtymap[x]) {
                x += 1;
                continue;
            }

            /*
             * Now find runs, so we can flush adjacent sectors
             * in a single operation.
             */
            baseSeg = FATSECTSEG(x);
            for (cnt = 1; ((x + cnt) < dirtymapsize) &&
                dirtymap[x + cnt];++cnt) {
                /*
                 * Can't write() across distinct
                 * chunks of FAT entries.
                 */
                if (FATSECTSEG(x + cnt) != baseSeg) {
                    break;
                }
            }

            // Seek to the right place, and write the data
            fatp = lookup(FATSECT(x));

            if (x < fatsize) {
                ECode ec = WriteDevice(
                    p_Sharedata->pBlkDev,
                    (((fatbase + x+p_Sharedata->bpb_base) << p_Sharedata->secshift) + off),
                    (cnt<< p_Sharedata->secshift), (PByte)fatp, NULL, NULL);
                if (FAILED(ec)) {
                    DPRINTF(("ERROR: Failed to Write from the offset:%d\n", \
                            ((fatbase + x) << p_Sharedata->secshift) + off));
                    return;
                }
            }
            else {
                DPRINTF(("WARNING: Overflow in writting FAT32 table."
                                " offset: %d, len: %d\n",
                                x, cnt));
            }

            /*
             * Advance x
             */
            x += cnt;
        }
    }

    /*
     * Clear dirtymap--everything's been flushed successfully
     */
    bzero(dirtymap, dirtymapsize);

    /*
     * Update info sector, if present
     */
    if (info) {
        /*
         * Copy over our rotor for allocation attempt starting point
         */
        if (2 == nxt_clust) {
            info->last = p_Sharedata->nclust + 1;
        }
        else {
            info->last = nxt_clust - 1;
        }

        /*
         * Write the info sector
         */
        ECode ec = WriteDevice(
                    p_Sharedata->pBlkDev,
                    (infobase+p_Sharedata->bpb_base) * p_Sharedata->secsz,
                    infosize, (PByte)info, NULL, NULL);
        if (FAILED(ec)) {
            DPRINTF(("ERROR: Failed to Write from the offset:%d\n", \
                    infobase * p_Sharedata->secsz));
            return;
        }
    }
}

fat32::fat32()
{
    fatv = NULL;
    fatlen = 0;
    nfatv = 0;
    fatvlen = 0;
    fatseg = 0;
    dirtymap = NULL;
    dirtymapsize = 0;
    nxt_clust = 2;
    fatbase = 0;
    infobase =0;
    infosize =0;
    p_Sharedata = NULL;
    info = NULL;
}

fat32::~fat32()
{
    if (fatv) {
        fat32_clearfatseg();
        free(fatv);
    }

    if (info) {
        free(info);
    }

    if (dirtymap != NULL) {
        free(dirtymap);
    }
    if (p_Sharedata)
        p_Sharedata->Release();
}

/*
 * Calculate the free space in disk
 */
UInt64 fat32::fat32_calcfreespace()
{
    UInt32 totalFreeClust = 0;
    UInt64 totalByte = 0;

    if (info && (info->free != 0xffffffff)
            && (info->free <= p_Sharedata->nclust)) {
        return ((UInt64)info->free) * p_Sharedata->clsize;
    }

    Int64 offset;
    fat32_t * buf;
    UInt32 clustCount = p_Sharedata->nclust + 1 - 2; // begin from 2nd cluster
    UInt32 countPerSeg = FATSEGSIZE / sizeof(fat32_t);
    UInt32 count = (clustCount + countPerSeg - 1) / countPerSeg;
    UInt32 base = 0;

    buf = (fat32_t *)malloc(FATSEGSIZE);
    if (NULL == buf) {
        return 0;
    }

    offset = (fatbase * p_Sharedata->secsz) +
                                    p_Sharedata->bpb_base * p_Sharedata->secsz;
    for (UInt32 i = 0; i < count; ++i) {
        ECode ec = ReadDevice(
                    p_Sharedata->pBlkDev, offset, FATSEGSIZE, (PByte)buf,
                    NULL, NULL);
        if (FAILED(ec)) {
            DPRINTF(("ERROR: Failed to Read from the offset:%d\n", offset));
            free(buf);
            return 0;
        }

        for (UInt32 j = 0; j < clustCount - base && j < countPerSeg; ++j) {
            if (buf[j] == 0) totalFreeClust++;
        }

        base += countPerSeg;
        offset += FATSEGSIZE;
    }

    free(buf);

    totalByte = ((UInt64)totalFreeClust) * p_Sharedata->clsize;
    return totalByte;
}

#if defined(_DEBUG)
void fat32::fat32_stat(UInt32 *Fat_secs)
{
    UInt32 i , cnt = 0;
    for (i = 0; i < dirtymapsize;++i) {
        if (dirtymap[i])
            ++cnt;
    }

    *Fat_secs = cnt;
}
#endif
